package com.xzk.ExpressInnWeb.web.mvc;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 映射器或映射池
 * 包含大量的网址与方法的对应关系
 */
public class HandlerMapping {

    // key: (String) 注解地址 即get(uri)方法中需要传入的uri
    // value: (MVCMapping) 映射地址
    private static Map<String, MVCMapping> data = new HashMap<>();

    public static MVCMapping get(String uri) {
        return data.get(uri);
    }

    /**
     * 加载：
     *
     * 读取配置文件的输入流
     * 将配置文件中指向的类中所有使用
     * ResponseBody和ResponseView注解的
     * 封装为MVCMapping对象
     *
     * 使用在DispatcherServlet的init(config)方法中
     *
     * @param is 输入流
     */
    public static void load(InputStream is){
        Properties ppt = new Properties();
        try {
            ppt.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 取出Properties存储的所有的值存入集合中
        Collection<Object> values = ppt.values();
        // 遍历values，即读取配置文件
        // values 存储的是配置文件中各键值对等号的右侧部分，即类的完整路径
        // 即获取的是配置文件中描述的一个个类
        for (Object class_ppt: values) {
            // 强制类型转换获得类名
            String className = (String) class_ppt;
            // 反射处理，创建对象，获取方法
            try {
                // forName()方法获得类
                // 加载配置文件中描述的每一个类
                Class c = Class.forName(className);
                // 调用无参构造方法创建上面获得的类的实例对象
                Object obj = c.getConstructor().newInstance();
                // 获取该类的所有(公有)方法
                Method[] methods = c.getMethods();
                for (Method method: methods) {
                    // 通过该方法的注解判断该方法的作用
                    // 获取所有注解并进行判断
                    Annotation[] annotations = method.getAnnotations();
                    if (annotations != null) {
                        for (Annotation annotation: annotations) {
                            if (annotation instanceof ResponseBody) {
                                // 说明此方法用于返回字符串给客户端
                                // 将注解对应的方法存入data集合，需要的key是注解的值(注解地址)
                                // 而data集合的值是MVCMapping对象(映射地址)，故需要先创建该对象：
                                MVCMapping mvcMapping = new MVCMapping(obj,method, ResponseType.TEXT);
                                /*
                                 * 如此即将
                                 * 反射获得的类c的实例对象obj
                                 * 以及现在遍历到达的方法method
                                 * 和该method通过注解获得的类型ResponseType.TEXT
                                 * (因该if代码块中处理的是ResponseBody类型的注解)
                                 * 封装为mvcMapping对象
                                 */
                                // 存入data集合:
                                MVCMapping put = data.put(((ResponseBody) annotation).value(), mvcMapping);
                                if (put != null) {
                                    // 存在重复的请求地址uri
                                    throw new RuntimeException("请求地址重复: " + ((ResponseBody) annotation).value());
                                }
                            } else if (annotation instanceof ResponseView) {
                                // 说明此方法用于返回界面(view，即JSP或HTML页面用于页面跳转)给客户端
                                MVCMapping mvcMapping = new MVCMapping(obj,method, ResponseType.VIEW);
                                MVCMapping put = data.put(((ResponseView) annotation).value(),mvcMapping);
                                if (put != null) {
                                    // 存在重复的请求地址uri
                                    throw new RuntimeException("请求地址重复: " + ((ResponseView) annotation).value());
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * 映射对象，
     * 每一个对象封装了一个方法，用于处理请求
     */
    public static class MVCMapping {
        private Object obj;
        // java.lang.reflect反射包中的类型
        private Method method;
        private ResponseType type;

        public MVCMapping() {
        }

        public MVCMapping(Object obj, Method method, ResponseType type) {
            this.obj = obj;
            this.method = method;
            this.type = type;
        }

        public Object getObj() {
            return obj;
        }

        public void setObj(Object obj) {
            this.obj = obj;
        }

        public Method getMethod() {
            return method;
        }

        public void setMethod(Method method) {
            this.method = method;
        }

        public ResponseType getType() {
            return type;
        }

        public void setType(ResponseType type) {
            this.type = type;
        }
    }
}
